import weka.core.Attribute;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Utils;
import weka.core.converters.ArffLoader;
import weka.classifiers.neural.lvq.model.CodebookVector;

import java.awt.Graphics;
import java.awt.List;
import java.io.File;
import java.util.ArrayList;

/**
 * Just testing....
 * 
 * 
 * @author
 */
public class SOMKMean {

	/**
	 * 
	 */
	private static final double theta = 0;

	/**
     * 	
     */
	private static final double delta = 0;

	/**
	 * predefined threshold value for the distance between a instance and its
	 * bmu
	 */
	private static final double epsilon = 0.005;

	public static void main(String[] args) throws Exception {

		// load training data
		System.out.print("Loading training Data...");
		ArffLoader loader = new ArffLoader();
		loader.setFile(new File("data/KDD_Datasets_ARFF/KDDTrain+.arff"));
		Instances structure = loader.getDataSet();
		structure.setClassIndex(structure.numAttributes() - 1);
		System.out.println(" Done");

		System.out.print("Normalizing data...");
		structure = normalizeData(structure);
		System.out.println(" Done");
		// train the som

		System.out.print("Building SOM... ");
		MySom som = new MySom();
		som.buildClassifier(structure);
		System.out.println(" Done");

		// save weightVectors
		System.out
				.print("Extract weightvectors and identifying normal and attack vectors...");
		CodebookVector[] weightVectors = som.getCodeBook();
		Instances weightInstances = new Instances(structure,
				weightVectors.length);
		// Convert Codebook weightVectors to Instances
		for (CodebookVector c : weightVectors) {
			weightInstances.add(codebookToInstance(c, structure));
		}
		System.out.println(" Done");
		
		System.out.println("DEBUG - BMU'S");
		
			System.out.println(weightInstances.toString());
	

	/*	// identify normal / attack clusters
		Instances attackCentroids = extractAttackCentroids(weightInstances);
		Instance normalCentroid = extractNormalCentroid(weightInstances);
		System.out.println("\\nnNormalcentroid: " + normalCentroid.toString());
		for (int i = 0; i < attackCentroids.numInstances(); i++) {
			System.out.println("Attackcentroid " + i + ": "
					+ attackCentroids.instance(i).toString());
		}
	 */
		System.out.print("\nBuilding KMeans clusterer... ");
		// kmean clusterer
		MySimpleKMeans kmean = new MySimpleKMeans();
		kmean.setNumClusters(4);
		// build kmeans clusterer with trainingset, attackcentroids and the
		// normal centriud (weightVectors)
		//kmean.buildClusterer(weightInstances, attackCentroids, normalCentroid);
		kmean.buildClusterer(weightInstances);
		System.out.println(" Done");

		System.out.println(kmean.toString());
		/*
		 * TODO / done construct clusters by kmeansclustering with the
		 * weightvector of each unit -> determine a normal cluster
		 */

		// --------------------------------------------------------------------------------------------------------------------------------------------
		// load test data
		loader.setFile(new File("data/KDD_Datasets_ARFF/KDDTest.arff"));
		Instances trainInstances = loader.getDataSet();
		trainInstances.setClassIndex(trainInstances.numAttributes() - 1);
		trainInstances = normalizeData(trainInstances);

		for (int i = 0; i < trainInstances.numInstances(); i++) {
			Instance instance = trainInstances.instance(i);

			if (kmean.distance(
					instance,
					codebookToInstance(som.getBmuForInstance(instance),
							trainInstances)) >= epsilon) {
				kmean.clusterInstance(instance);
				continue;
			} else {
				som.classifyInstance(instance);
				kmean.clusterInstance(instance);
			}
			
			kmean.updateAttackClusters();
			
		}

		/*
		 * if(sum(distance(wi(t), wi(t0)) >= theta){ break; } else{
		 * updateCentroids(); if(SS1/SS2 <= r){ break; } else{
		 * splitTheNormalCluster(); determineNewAttackCluster(); }
		 */

	}

	/**
	 * 
	 * @param weightInstances
	 * @return the interpolatet instance over all normal-class vectors
	 */
	private static Instance extractNormalCentroid(Instances weightInstances) {
		Instances instances = new Instances(weightInstances, 100);
		for (int i = 0; i < weightInstances.numInstances(); i++) {
			Instance instance = weightInstances.instance(i);
			if (0.0 == instance.classValue()) {
				instances.add(instance);
			}
		}
		return interpolate(instances);
	}

	/**
	 * 
	 * @param weightInstances
	 * @return a Instances object with all the anomaly-class vectors
	 */
	private static Instances extractAttackCentroids(Instances weightInstances) {
		Instances instances = new Instances(weightInstances, 100);
		for (int i = 0; i < weightInstances.numInstances(); i++) {
			Instance instance = weightInstances.instance(i);
			if (1.0 == instance.classValue()) {
				instances.add(instance);
			}
		}
		return instances;
	}

	/**
	 * replaces non numeric attributes with numeric values for the given
	 * dataset. ! the last attribute of kdd99 is element {normal,anomalie} -> {1
	 * , 0} for later use !
	 * 
	 * How to calculate the numeric value of a non numeric attribute:
	 * 
	 * count the number of occurrences of the given non numeric attribute value
	 * in the whole dataset in step two replace it with 1/#occurences.
	 * 
	 * @param Instances
	 *            Object that holds the original data with non numeric
	 *            attributes structure
	 * @return Instances Object that holds only data with numeric attributes
	 */
	private static Instances normalizeData(Instances structure) {
		// Iterate over atttributes in the dataset
		for (int j = 0; j < structure.numAttributes() - 1; j++) {
			// get attribute
			Attribute a = structure.attribute(j);
			// if atribute is nominal do:
			if (a.isNominal()) {
				ArrayList<Integer> countList = new ArrayList<Integer>();
				ArrayList<String> stringList = new ArrayList<String>();
				// for every instance in the dataset
				for (int i = 0; i < structure.numInstances(); i++) {
					String _attValue = structure.instance(i).stringValue(j);
					// check if the attribute value is already in the stringList
					// / if not add it, ad a new node to the countList
					if (!stringList.contains(_attValue)) {
						stringList.add(_attValue);
						countList.add(0);
					}
					// increment the value in the count list for every occurence
					// of the value of the given attribute
					countList.set(stringList.indexOf(_attValue),
					countList.get(stringList.indexOf(_attValue)) + 1);
				}

				// now we have a list with the number of occurences of values
				// for the current attribute.
				// todo: change type of the attribute to numeric and change
				// values for each instance
				// funcitios deleteAttributeAt(int position) : Instances -
				// insertAttributeAt(int position): Instances

				// initilise new attribute with the name of the act attribute
				Attribute numericAtt = new Attribute(a.name());

				// set new numeric values
				for (int i = 0; i < structure.numInstances(); i++) {
					String _attValue = structure.instance(i).stringValue(j);
					structure.instance(i).setValue(j,
							1 / countList.get(stringList.indexOf(_attValue)));
				}

				// replace attribute
				structure.deleteAttributeAt(j);
				structure.insertAttributeAt(numericAtt, j);

				// set new Dataset for all for instances
				for (int i = 0; i < structure.numInstances(); i++) {
					structure.instance(i).setDataset(structure);
				}

			}
		}
		return structure;
	}

	/**
	 * casts a CodebookVector into an instance object. 
	 * header out of given data
	 * 
	 * @param v
	 * @return Instance
	 */
	private static Instance codebookToInstance(CodebookVector v, Instances struc) {
		Instance inst = new Instance(v.getAttributes().length);
		inst.setDataset(struc);
		for (int i = 0; i < v.getAttributes().length; i++) {
			inst.setValue(i, v.getAttributes()[i]);
		}
		return inst;
	}

	/**
	 * Interpolates the instances....
	 * 
	 * TODO: Implement :) Actualli returns the first element in the dataset
	 * 
	 * @param normal
	 *            a set of normal class instances
	 * @return the interpolatet normal-instance
	 */
	private static Instance interpolate(Instances normal) {
		return normal.firstInstance();
	}

}
